package edu.udo.sopra10.chaturaji.entities.coordinates;

import static edu.udo.sopra10.chaturaji.util.ExceptionUtil.assertInRange;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;

import edu.udo.sopra10.chaturaji.util.SimpleClonable;

/**
 * A bounds-checking {@link Coordinate}. This adds bounds checking to
 * coordinates, thereby limiting the range of permitted coordinate values.
 * 
 * @author Simon Dierl
 */
public class BoundedCoordinate extends SimpleClonable implements Coordinate {

	/**
	 * The minimum value for x- and y-coordinates.
	 */
	private final byte lowerBound;

	/**
	 * The maximum value for x- and y-coordinates.
	 */
	private final byte upperBound;

	/**
	 * The x-coordinate of this coordinate.
	 */
	private byte x;

	/**
	 * The y-coordinate of this coordinate.
	 */
	private byte y;

	/**
	 * Create a new coordinate with bound checking using the specified bounds
	 * and coordinates.
	 * 
	 * @param lowerBound
	 *            the minimum value for x- and y-coordinates stored.
	 * @param upperBound
	 *            the maximum value for x- and y-coordinates stored.
	 * @param x
	 *            the initial x-coordinate, must be in the bounds.
	 * @param y
	 *            the initial y-coordinate, must be in the bounds.
	 */
	public BoundedCoordinate(final byte lowerBound, final byte upperBound,
			final byte x, final byte y) {
		this.lowerBound = lowerBound;
		this.upperBound = upperBound;
		setXY(x, y);
	}

	@Override
	public boolean equals(final Object obj) {
		if (obj == this) {
			return true;
		} else if ((obj == null) || !(obj instanceof Coordinate)) {
			return false;
		} else if (obj instanceof BoundedCoordinate) {
			final BoundedCoordinate coordinate = (BoundedCoordinate) obj;
			return new EqualsBuilder().append(x, coordinate.x)
					.append(y, coordinate.y).isEquals();
		} else {
			final Coordinate coordinate = (Coordinate) obj;
			return new EqualsBuilder().append(x, coordinate.getX())
					.append(y, coordinate.getY()).isEquals();
		}
	}

	@Override
	public byte getX() {
		return x;
	}

	@Override
	public byte getY() {
		return y;
	}

	@Override
	public int hashCode() {
		return new HashCodeBuilder(19, 53).append(x).append(y).toHashCode();
	}

	@Override
	public void setX(final byte x) {
		this.x = assertInRange(x, lowerBound, upperBound, "x");
	}

	@Override
	public void setXY(final byte x, final byte y) {
		setX(x);
		setY(y);
	}

	@Override
	public void setY(final byte y) {
		this.y = assertInRange(y, lowerBound, upperBound, "y");
	}

	@Override
	public void shift(final Coordinate position) {
		shiftXY(position.getX(), position.getY());
	}

	@Override
	public byte shiftX(final byte x) {
		setX((byte) (this.x + x));
		return this.x;
	}

	@Override
	public void shiftXY(final byte x, final byte y) {
		shiftX(x);
		shiftY(y);
	}

	@Override
	public byte shiftY(final byte y) {
		setY((byte) (this.y + y));
		return this.y;
	}

	@Override
	public String toString() {
		return new StringBuilder("(").append(x).append(",").append(y)
				.append(")").toString();
	}

}
